package com.apes.scm.settle.payment.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.apes.framework.api.DomainService;
import com.apes.framework.api.SpringManager;
import com.apes.framework.jpa.generator.service.SequenceService;
import com.apes.framework.jpa.spec.JpaDsl;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.util.Arith;
import com.apes.framework.util.MapUtil;
import com.apes.scm.account.ledger.service.LedgerService;
import com.apes.scm.masterdata.finance.model.PaymentMethodType;
import com.apes.scm.masterdata.finance.repository.PaymentMethodRepository;
import com.apes.scm.masterdata.frame.model.Dept;
import com.apes.scm.masterdata.frame.repository.DeptRepository;
import com.apes.scm.masterdata.party.model.PartyRole;
import com.apes.scm.masterdata.party.repository.PartyRoleRepository;
import com.apes.scm.settle.payment.model.*;
import com.apes.scm.settle.payment.repository.*;
import com.apes.scm.settle.payment.repository.PaymentExecuteRecordRepository;
import com.apes.scm.settle.payment.repository.PaymentRegisterItemRepository;
import com.apes.scm.settle.payment.repository.PaymentRegisterRepository;
import com.apes.scm.settle.payment.repository.PaymentRepository;
import com.apes.scm.settle.settle.model.SettleSimple;
import com.apes.scm.settle.settle.respository.SettleSimpleRepository;
import com.apestech.framework.json.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 付款服务
 *
 * @author wuq
 * @create 2018-11-02 17:25
 */
@Service("paymentService")
public class PaymentService extends DomainService {

    @Autowired
    private PaymentRepository paymentRepository;

    @Autowired
    private PaymentItemRepository paymentItemRepository;

    @Autowired
    private PaymentExecuteRecordRepository paymentExecuteRecordRepository;

    @Autowired
    private LedgerService ledgerService;

    @Autowired
    private PaymentRegisterRepository paymentRegisterRepository;

    @Autowired
    private PartyRoleRepository partyRoleRepository;


    @Autowired
    private SettleSimpleRepository settleSimpleRepository;

    public Payment create(SimpleRequest request) {
        Payment payment = request.getO(Payment.class);
        payment.getPaymentItemSet().forEach(v -> {
            PaymentItem paymentItem = paymentItemRepository.findByOrigin(v.getOrigin());
            if (Objects.nonNull(paymentItem)) throw new RuntimeException(paymentItem.getPayment().getId() + ",已创建过付款单，请确认！");
        });
        removeNoSelectProduct(payment);

        payment.setState("sent");
        payment.computeAmt();

        // 回写执行记录的状态
        setPaymentExecuteRecordState(payment, "approve", "notPaying");


        return paymentRepository.saveAndFlush(payment);
    }

    public void checkData(SimpleRequest request) {
        String id = request.get("id");
        int version = request.get("version");

        // 从数据库中取数，检查数据
        Payment oldPayment = paymentRepository.findOne(id);
        if (oldPayment == null) throw new RuntimeException("付款单【" + id + "】在系统中不存在。");
        if (oldPayment.getVersion() != version) throw new RuntimeException("记录已更新或已删除，请重新提交！");
    }

    /**
     * 更新采购付款单
     * 更新时：先将之前的冻结的数据还原，然后写入新的数据
     *
     * @param request
     * @return
     */
    public Payment update(SimpleRequest request) {
        checkData(request); // 检查数据
        Payment payment = request.getO(Payment.class);
        removeNoSelectProduct(payment);
        payment.computeAmt();

        Payment reduction = paymentRepository.findOne(payment.getId());
        // 还原执行记录的状态
        setPaymentExecuteRecordState(reduction, "create", "approve");

        //重写执行记录的状态
        setPaymentExecuteRecordState(payment, "approve", "notPaying");
        return paymentRepository.saveAndFlush(payment);
    }

    public String delete(SimpleRequest request) {
        checkData(request); // 检查数据
        Payment payment = request.getO(Payment.class);

        // 回写执行记录的状态
        setPaymentExecuteRecordState(payment, "create", "approve");

        payment.setState("delete");
        payment.setDeleteDate(new Date());
        payment.setDeleteUID(getPerson(request));
        paymentRepository.saveAndFlush(payment);
        return "删除成功！";
    }

    /**
     * 审核操作
     *
     * @param request
     * @return
     */
    public Payment approve(SimpleRequest request) {
        checkData(request); // 检查数据
        Payment payment = request.getO(Payment.class);

        // 回写执行记录的状态
        setPaymentExecuteRecordState(payment, "done", "executive");

        // 执行付款
        pay(payment);

        payment.setState("approve");
        payment.setApproveDate(new Date());
        if(payment.getApproveUID() == null) payment.setApproveUID(getPerson(request));
        payment = paymentRepository.saveAndFlush(payment);

        // 发送事件
        sendPaymentMessage(payment);

        //回写2.0收款金额
        invoke("merchant.settlement.payment", MapUtil.mapper("data", MapUtil.mapper("payment", payment)));

        //回写预支单
        invoke("fn.businessDebitNote.payment", MapUtil.mapper("data", MapUtil.mapper("payment", payment)));

        //回写排款单付款金额
        invoke("fn.disbursement.payRewrite", MapUtil.mapped(payment));

        //回写预付款申请单
        invoke("advance.order.payment", MapUtil.mapper("data", MapUtil.mapper("payment", payment)));
        return payment;
    }

    public void sendPaymentMessage(Payment payment) {
        for (PaymentItem item : payment.getPaymentItemSet()) {
            Map map = OriginMapping(item);
            // 发送事件消息
            sendSynEvent("event:payment.pay", map);
        }
    }

    public Map OriginMapping(PaymentItem item) {
        Map map = new HashMap();
        map.put("origin", item.getOrigin());
        map.put("originBusinessType", item.getOriginBusinessType());
        map.put("amount", item.getAmount());
        return map;
    }

    /**
     * 回写执行记录的状态
     *
     * @param payment
     * @param executeRecordState 记录表状态
     * @param registerState      供应商付款登记状态
     */
    private void setPaymentExecuteRecordState(Payment payment, String executeRecordState, String registerState) {
        payment.getPaymentItemSet().stream()
                .map(paymentItem -> paymentItem.getPaymentExecuteRecord().getId()).distinct()
                .forEach( paymentExecuteRecordId -> {
                    if (StringUtils.isEmpty(paymentExecuteRecordId)) return;

                    // 回写执行记录表
                    PaymentExecuteRecord paymentExecuteRecord = paymentExecuteRecordRepository.findOne(paymentExecuteRecordId);
                    paymentExecuteRecord.setState(executeRecordState);
                    paymentExecuteRecordRepository.saveAndFlush(paymentExecuteRecord);

                    //  回写待付款状态
                    PaymentRegister paymentRegister = paymentRegisterRepository.findOne(paymentExecuteRecord.getOrigin());
                    if (!StringUtils.isEmpty(paymentRegister)) {
                        paymentRegister.setState(registerState);
                        paymentRegisterRepository.saveAndFlush(paymentRegister);
                    }

                });
    }

    /**
     * 付款操作
     * 先还原已冻结的金额，然后将已冻结的金额转为已付款金额
     *
     * @param payment
     * @return
     */
    public void pay(Payment payment) {

        if (payment.getPaymentMethodSet() == null || payment.getPaymentMethodSet().isEmpty())
            throw new RuntimeException("付款方式明细为空！");

        double sumItemsAmount = payment.getPaymentItemSet().stream().map(PaymentItem::getAmount).reduce(Double::sum).get();
        double sumMethodAmount = payment.getPaymentMethodSet().stream().map(PaymentMethod::getAmount).reduce(Double::sum).get();

        if (sumMethodAmount == 0) {
            throw new RuntimeException("收付款方式合计金额为 0，请检查！");
        }

        if (Arith.round(sumItemsAmount, 2) != Arith.round(sumMethodAmount, 2)) {
            throw new RuntimeException("单据明细付款合计金额与付款方式合计金额不相等！");
        }

        Map<String, Object> paramMap = MapUtil.mapper("company", payment.getCompany(), "partner", payment.getPartner());

        // 判断收款方式为预付款的，写入预付款总账
        payment.getPaymentMethodSet().forEach(paymentMethod -> {
            String paymentType = paymentMethod.getPaymentMethodType().getPaymentType().getId();
            // 预付款 与 预收款
            if ("prepaid".equals(paymentType) || "prepayment".equals(paymentType)) {
                ledgerService.transaction(paramMap, payment.getId(), payment.getInnerBusinessType(), new Date(),
                        "prepayment_unsettled", "prepayment_settled", paymentMethod.getAmount());
            }
        });
    }

    /**
     * 提供界面查询供应商或者客户未清项货款
     *
     * @param request
     * @return
     */
    public Map<String, Double> findPrePayment(SimpleRequest request) {
        Payment payment = request.getO(Payment.class);
        Map<String, Double> returnMap = ledgerService.summary(
                MapUtil.mapper("company", payment.getCompany(), "partner", payment.getPartner()));
        return returnMap;
    }

    @Autowired
    private PaymentMethodRepository paymentMethodRepository;

    public Payment fillPaymentMethod(SimpleRequest request) {
        Payment payment = request.getO(Payment.class);
        PaymentMethod paymentMethod = new PaymentMethod();
        String id = SpringManager.getBean(SequenceService.class).getId(PaymentMethod.CONST_TABLE_NAME);
        paymentMethod.setId(id);
        paymentMethod.setAmount(payment.getAmount());
        PaymentMethodType paymentMethodTyp = paymentMethodRepository.findOne("01");
        paymentMethod.setPaymentMethodType(paymentMethodTyp);
        payment.getPaymentMethodSet().add(paymentMethod);
        return payment;
    }


    private void removeNoSelectProduct(Payment payment) {
        payment.getPaymentItemSet().removeIf(paymentItem -> !paymentItem.isSelected());
        if (payment.getPaymentItemSet() == null || payment.getPaymentItemSet().size() == 0)
            throw new RuntimeException("保存失败，单据明细不能为空");
    }

    public com.apestech.framework.json.JSONObject query(SimpleRequest request) throws Exception {
        JSONObject jo = request.getJO();
        JSONArray condition = jo.getJSONArray("condition");
        if (condition != null) {
            for (int i = 0; i < condition.size(); i++) {
                JSONObject newJsonObject = (JSONObject) condition.getJSONObject(i).clone();
                if ("APPLY_DATE".equals(condition.getJSONObject(i).getString("field")) &&
                        "GTE".equals(condition.getJSONObject(i).getString("operator"))
                ) {
                    newJsonObject.put("field", "APPLY_DATE_BEGIN");
                    newJsonObject.put("operator", "EQ");
                    newJsonObject.remove("type");
                    condition.add(newJsonObject);
                } else if ("APPLY_DATE".equals(condition.getJSONObject(i).getString("field")) &&
                        "LTE".equals(condition.getJSONObject(i).getString("operator"))
                ) {
                    newJsonObject.put("operator", "EQ");
                    newJsonObject.remove("type");
                    newJsonObject.put("field", "APPLY_DATE_END");
                    condition.add(newJsonObject);
                }

            }
            jo.put("condition", condition);
        }
        return JSON.toJSON(jo.toJSONString());
    }

    public void createSettleSimple(Payment payment) {
        removeNoSelectProduct(payment);
        payment.setState("sent");
        payment.computeAmt();

        SettleSimple settleSimple = settleSimpleRepository.getOne(payment.getSource());
        Set<PaymentMethod> paymentMethodSet = new HashSet();
        settleSimple.getPaymentMethodSet().forEach(v -> {
            PaymentMethod paymentMethod = new PaymentMethod();
            paymentMethod.setPaymentMethodType(v.getPaymentMethodType());
            paymentMethod.setBankAccount(v.getBankAccount());
            paymentMethod.setAmount(v.getAmount());
            paymentMethod.setNote(v.getNote());
            paymentMethodSet.add(paymentMethod);
        });
        payment.setPaymentMethodSet(paymentMethodSet);

        // 回写执行记录的状态
        setPaymentExecuteRecordState(payment, "approve", "notPaying");
        approveSettleSimple(paymentRepository.saveAndFlush(payment));
    }


    /**
     * 审核操作
     *
     * @param payment
     * @return
     */
    public void approveSettleSimple(Payment payment) {
        payment.setState("purchase");
        payment.setApproveDate(new Date());
        payment.setApproveUID(partyRoleRepository.findOne(payment.getCreator()));


        setPaymentExecuteRecordState(payment, "done", "approve");
        paymentRepository.saveAndFlush(payment);
    }
}
